﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.Threading;
using System.Diagnostics;
using System.Windows.Media.Imaging;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using WpfCore = System.Windows;
using WpfMedia = System.Windows.Media;
using WpfControls = System.Windows.Controls;
using WpfInput = System.Windows.Input;

namespace CrayonsBox.Drawing
{
    //! All drawing things take place in this class . Still , This class is designed by a stupid dev
    public class Document
    {    
        public enum ToolMode
        {
            MoveViewer,
            ZoomViewer,

            MoveSelection,
            ResizeSelection,
            
            MoveLayer,

            GetAlphaColor,

            AnimateSprite
        }

        //+------------------------------------------------------------------------------
        //| @brief : Represent a drawing target . 
        //|          Inheriting Drawable class to create more drawable things.
        //+------------------------------------------------------------------------------
        public class Drawable : INotifyPropertyChanged
        {
            public class SizingGrip
            {
                                                    // PRIORITY
                public const int TopLeft = 0;       //  [High]
                public const int TopRight = 1;      //    |
                public const int BottomRight = 2;   //    |
                public const int BottomLeft = 3;    //    |
                public const int Left = 4;          //    |
                public const int Top = 5;           //    |
                public const int Right = 6;         //    |
                public const int Bottom = 7;        //    |
                public const int None = 8;          //  [Low]

                protected Point m_center;

                public Point Center 
                {
                    get
                    {
                        return m_center;
                    }

                    set
                    {
                        if (!m_center.Equals(value))
                        {
                            m_center = value;
                            if (PositionChanged != null)
                                PositionChanged(this, new EventArgs());
                        }
                    }
                }
                public Size Radii { get; set; }
                public Size ActualRadii { get; set; }

                public event EventHandler PositionChanged;
            }

            public event PropertyChangedEventHandler PropertyChanged;

            protected Document m_container;

            protected Rectangle m_rect;

            protected SizingGrip[] m_grips;

            protected int m_gripidx;

            public event EventHandler<MouseEventArgs> MoveDown;
            
            public event EventHandler<MouseEventArgs> MouseMove;
            
            public event EventHandler<MouseEventArgs> MouseUp;

            public Document Doc
            {
                get
                {
                    return m_container;
                }
            }

            public virtual Boolean IsVisible { get; set; }

            public virtual Boolean IsSelected { get; set; }

            public virtual Int32 ZIndex { get; set; }

            public virtual Point Location 
            {
                get
                {
                    return m_rect.Location;
                }

                set
                {
                    m_rect.Location = value;
                    Boundary = m_rect;
                }
            }
        
            public virtual Size Dimension 
            {
                get
                {
                    return m_rect.Size;
                }

                set
                {
                    m_rect.Size = value;
                    Boundary = m_rect;
                }
            }

            public virtual Rectangle Boundary 
            {
                get
                {
                    return m_rect;
                }

                set
                {
                    m_rect = value;

                    Rectangle r = new Rectangle(Math.Min(m_rect.Left, m_rect.Right), Math.Min(m_rect.Top, m_rect.Bottom),
                                                Math.Abs(m_rect.Width), Math.Abs(m_rect.Height));

                    m_grips[SizingGrip.TopLeft].Center = new Point(r.Left, r.Top);
                    m_grips[SizingGrip.TopRight].Center = new Point(r.Right, r.Top);
                    m_grips[SizingGrip.BottomLeft].Center = new Point(r.Left, r.Bottom);
                    m_grips[SizingGrip.BottomRight].Center = new Point(r.Right, r.Bottom);
                    m_grips[SizingGrip.Left].Center = new Point(r.Left, (r.Top + r.Bottom) / 2);
                    m_grips[SizingGrip.Right].Center = new Point(r.Right, (r.Top + r.Bottom) / 2);
                    m_grips[SizingGrip.Top].Center = new Point((r.Left + r.Right) / 2, r.Top);
                    m_grips[SizingGrip.Bottom].Center = new Point((r.Left + r.Right) / 2, r.Bottom);

                    if (m_grips[SizingGrip.Left].Radii.Height == -1)
                        m_grips[SizingGrip.Left].ActualRadii = new Size(m_grips[SizingGrip.Left].Radii.Width, m_rect.Height / 2);

                    if (m_grips[SizingGrip.Right].Radii.Height == -1)
                        m_grips[SizingGrip.Right].ActualRadii = new Size(m_grips[SizingGrip.Right].Radii.Width, m_rect.Height / 2);

                    if (m_grips[SizingGrip.Top].Radii.Width == -1)
                        m_grips[SizingGrip.Top].ActualRadii = new Size(m_rect.Width / 2, m_grips[SizingGrip.Top].Radii.Height);

                    if (m_grips[SizingGrip.Bottom].Radii.Width == -1)
                        m_grips[SizingGrip.Bottom].ActualRadii = new Size(m_rect.Width / 2, m_grips[SizingGrip.Bottom].Radii.Height);

                    m_grips[SizingGrip.TopLeft].ActualRadii = m_grips[SizingGrip.TopLeft].Radii;
                    m_grips[SizingGrip.TopRight].ActualRadii = m_grips[SizingGrip.TopRight].Radii;
                    m_grips[SizingGrip.BottomLeft].ActualRadii = m_grips[SizingGrip.BottomLeft].Radii;
                    m_grips[SizingGrip.BottomRight].ActualRadii = m_grips[SizingGrip.BottomRight].Radii;

                    NotifyPropertyChanged("Location");
                    NotifyPropertyChanged("Dimension");
                }
            }

            public virtual Object Reserved { get; set; }

            public int SelectedGripIndex
            {
                get
                {
                    return m_gripidx;
                }

                set
                {
                    if (m_gripidx != value && m_gripidx != SizingGrip.None)              
                        m_grips[m_gripidx].PositionChanged -= DrawableGrip_PositionChanged;

                    if(value != SizingGrip.None)
                        m_grips[value].PositionChanged += DrawableGrip_PositionChanged;

                    if (this.IsSelected)
                        m_gripidx = value;
                    else
                        m_gripidx = SizingGrip.None;
                }
            }

            public SizingGrip SelectedGrip
            {
                get
                {
                    if (m_gripidx.Equals(SizingGrip.None))
                        return null;
                    return m_grips[m_gripidx];
                }
            }

            public SizingGrip[] SizingGrips
            {
                get
                {
                    return m_grips;
                }
            }

            private void DrawableGrip_PositionChanged(object sender, EventArgs e)
            {
                SizingGrip sg = sender as SizingGrip;

                Rectangle rec = new Rectangle(Math.Min(m_rect.Left, m_rect.Right), Math.Min(m_rect.Top, m_rect.Bottom),
                                               Math.Abs(m_rect.Width), Math.Abs(m_rect.Height));

                sg.PositionChanged -= DrawableGrip_PositionChanged;

                int l = rec.Left;
                int t = rec.Top;
                int r = rec.Right;
                int b = rec.Bottom;

                if (m_gripidx.Equals(SizingGrip.Left) || m_gripidx.Equals(SizingGrip.TopLeft) || m_gripidx.Equals(SizingGrip.BottomLeft))
                    l = Math.Min(sg.Center.X, rec.Right);

                if (m_gripidx.Equals(SizingGrip.Top) || m_gripidx.Equals(SizingGrip.TopLeft) || m_gripidx.Equals(SizingGrip.TopRight))
                    t = Math.Min(sg.Center.Y, rec.Bottom);

                if (m_gripidx.Equals(SizingGrip.Right) || m_gripidx.Equals(SizingGrip.TopRight) || m_gripidx.Equals(SizingGrip.BottomRight))
                    r = Math.Max(sg.Center.X, rec.Left);

                if (m_gripidx.Equals(SizingGrip.Bottom) || m_gripidx.Equals(SizingGrip.BottomLeft) || m_gripidx.Equals(SizingGrip.BottomRight))
                    b = Math.Max(sg.Center.Y, rec.Top);

                Boundary = Rectangle.FromLTRB(l, t, r, b);

                sg.PositionChanged += DrawableGrip_PositionChanged;
            }

            public Drawable(Document doc)
            {
                m_container = doc;
            }

            public Drawable() 
            {
                m_grips = new SizingGrip[SizingGrip.None];
                for (int i = 0; i < SizingGrip.None; i++)
                {
                    m_grips[i] = new SizingGrip();
                }

                m_grips[SizingGrip.TopLeft].Radii = new Size(2, 2);
                m_grips[SizingGrip.TopRight].Radii = new Size(2, 2);
                m_grips[SizingGrip.BottomLeft].Radii = new Size(2, 2);
                m_grips[SizingGrip.BottomRight].Radii = new Size(2, 2);       
                m_grips[SizingGrip.Left].Radii = new Size(1, -1);
                m_grips[SizingGrip.Right].Radii = new Size(1, -1);
                m_grips[SizingGrip.Top].Radii = new Size(-1, 1);
                m_grips[SizingGrip.Bottom].Radii = new Size(-1, 1);
            }

            public virtual void Draw() { }

            public virtual void OnDraw(Document doc) { /*Override to draw*/ }

            public virtual void OnUpdate(int elapsedMs) { /*Override to update*/ }
      
            protected void NotifyPropertyChanged(String info)
            {
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs(info));
                }
            }
        }// __end__ Drawable


        //+------------------------------------------------------------------------------
        //| @brief : Layer class is used to group Drawable objects .
        //|          Drawable objects in same group will share same draw order
        //+------------------------------------------------------------------------------
        public class Layer
        {
            private List<Drawable> m_drawables;

            private String m_name;

            public List<Drawable> Drawables
            {
                get
                {
                    return m_drawables;
                }
            }

            public String Name 
            {
                get
                {
                    return m_name;
                }
            }

            public Boolean IsVisible { get; set; }

            public Layer(String name)
            {
                m_name = name;
                m_drawables = new List<Drawable>();
                IsVisible = false;
            }

            public void DepthSort()
            {
                m_drawables.Sort(delegate(Drawable ldr, Drawable rdr)
                {
                    return rdr.ZIndex - ldr.ZIndex;
                });
            }
        }// __end__ Layer


        //+------------------------------------------------------------------------------
        //| @brief : Manage the Layers of Document
        //+------------------------------------------------------------------------------
        public class LayerCollection : IList<Layer>
        {
            private List<Layer> m_container;
            private Dictionary<String, Int32> m_indexer;

            public LayerCollection()
            {
                m_container = new List<Layer>();
                m_indexer = new Dictionary<String, Int32>();
            }

            public int IndexOf(Layer item)
            {
                return m_container.IndexOf(item);
            }

            public void Insert(int index, Layer item)
            {
                m_container.Insert(index, item);
            }

            public void RemoveAt(int index)
            {
                String name = m_container[index].Name;
                if (m_indexer.Keys.Contains(name))
                    m_indexer.Remove(name);
                m_container.RemoveAt(index);
            }

            public Layer this[int index]
            {
                get
                {
                    return m_container[index];
                }
                set
                {
                    m_container[index] = value;
                }
            }

            public Layer this[String name]
            {
                get
                {
                    if (m_indexer.Keys.Contains(name))
                        return m_container[m_indexer[name]];
                    return null;
                }

                set
                {
                    if (m_indexer.Keys.Contains(name))
                        m_container[m_indexer[name]] = value;
                    else
                    {
                        m_container.Add(value);
                        m_indexer[name] = m_container.Count - 1;
                    }
                }
            }

            public void Add(Layer item)
            {
                this[item.Name] = item;
            }

            public void Clear()
            {
                m_container.Clear();
                m_indexer.Clear();
            }

            public bool Contains(Layer item)
            {
                return m_container.Contains(item);
            }

            public void CopyTo(Layer[] array, int arrayIndex)
            {
                throw new NotImplementedException();
            }

            public int Count
            {
                get { return m_container.Count; }
            }

            public bool IsReadOnly
            {
                get { return false; }
            }

            public bool Remove(Layer item)
            {
                if (m_container.Remove(item))
                {
                    m_indexer.Remove(item.Name);
                }
                return false;
            }

            public IEnumerator<Layer> GetEnumerator()
            {
                return m_container.GetEnumerator();
            }

            IEnumerator IEnumerable.GetEnumerator()
            {
                return m_container.GetEnumerator();
            }

            public void SetLayerIndex(Layer layer, Int32 index)
            {
                if (!m_container.Contains(layer))
                    return;

                if (m_indexer[layer.Name] == index)
                    return;

                if (index >= m_container.Count)
                    index = m_container.Count - 1;

                m_container.Remove(layer);
                m_container.Insert(index, layer);
                m_indexer[layer.Name] = index;
            }
        }// __end__ LayerCollection


        //+------------------------------------------------------------------------------
        //| @brief : Private class to draw selection zone
        //+------------------------------------------------------------------------------
        private class Selection : Drawable
        {
            public ObservableCollection<Drawable> SelectedDrawables { get; set; }

            public Selection()
            {
                SelectedDrawables = new ObservableCollection<Drawable>();
            }

            public override void OnDraw(Document doc)
            {
                if (Boundary.Width * Boundary.Height == 0)
                    return;

                Rectangle fixedRec = new Rectangle(
                    (int)(Math.Min(Boundary.Left, Boundary.Right) * doc.ZoomRate),
                    (int)(Math.Min(Boundary.Top, Boundary.Bottom) * doc.ZoomRate),
                    (int)(Math.Abs(Boundary.Width) * doc.ZoomRate),
                    (int)(Math.Abs(Boundary.Height) * doc.ZoomRate)
                );

                Matrix m = doc.Painter.Transform;

                doc.Painter.ResetTransform();

                doc.Painter.TranslateTransform(-doc.Viewport.X * doc.ZoomRate, -doc.Viewport.Y * doc.ZoomRate);

                doc.Painter.FillRectangle(new SolidBrush(Color.FromArgb(64, 51, 153, 255)), fixedRec);

                doc.Painter.DrawRectangle(new Pen(Color.FromArgb(51, 153, 255), 1), fixedRec);

                doc.Painter.Transform = m;
            }
        }// __end__ Selection

        public static int MaxFPS = 32;

        protected WriteableBitmap m_display;
        protected WpfControls.Image HostImage { get; set; }

        protected Graphics m_graph;
        protected Bitmap m_content;

        protected Thread m_thread;
        protected Mutex m_mutex;
        protected Stopwatch m_watch;

        protected LayerCollection m_layers;

        protected DepricatedLayerCollection m_oldLayers;

        protected SelectionCollection m_selections;

        protected Point m_relative;

        protected Rectangle m_viewer;

        public Graphics Painter
        {
            get
            {
                return m_graph;
            }
        }

        public Rectangle Viewport
        {
            get
            {
                return m_viewer;
            }
        }

        public ToolMode CurrentTool { get; set; }

        public Boolean ShowAllSelection { get; set; }

        public Boolean IsAxesVisible { get; set; }

        private Boolean IsDragDrop { get; set; } 

        public float ZoomRate { get; set; }

        public ObservableCollection<Drawable> SelectedItems
        {
            get
            {
                return (m_layers["selection"].Drawables.First() as Selection).SelectedDrawables;
            }
        }

        public SelectionCollection Selections
        {
            get { return m_selections; }
        }

        public LayerCollection Layers
        {
            get { return m_layers; }
        }

        public DepricatedLayerCollection OldLayers
        {
            get
            {
                return m_oldLayers;
            }
        }

        public Document(WpfControls.Image imgHost)
        {
            if (!(imgHost is WpfControls.Image))
                return;

            ZoomRate = 1.0f;
            IsAxesVisible = true;
            CurrentTool = ToolMode.ResizeSelection;
            
            HostImage = imgHost;

            m_display = null;

            m_viewer = Rectangle.Empty;

            m_selections = new SelectionCollection(this);

            m_layers = new LayerCollection();
            m_layers.Add(new Layer("selection"));
            m_layers["selection"].IsVisible = true;
            m_layers["selection"].Drawables.Add(new Selection());
            (m_layers["selection"].Drawables.First() as Selection).SelectedDrawables.CollectionChanged += SelectedDrawables_CollectionChanged;

            m_oldLayers = new DepricatedLayerCollection(this);

            m_thread = new Thread(new ThreadStart(this.UpdateScene));
            
            m_mutex = new Mutex(false);
        }

        private void SelectedDrawables_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == NotifyCollectionChangedAction.Add)
            {
                foreach (Drawable d in e.NewItems)
                    d.IsSelected = true;
            }

            if (e.Action == NotifyCollectionChangedAction.Remove)
            {
                foreach (Drawable d in e.OldItems)
                {
                    d.IsSelected = false;
                    d.Reserved = null;
                }
            }

            if (e.Action == NotifyCollectionChangedAction.Reset)
            {
                foreach (Layer l in m_layers)
                    foreach (Drawable dr in l.Drawables)
                    {
                        dr.IsSelected = false;
                        dr.Reserved = null;
                    }
            }
        }

        //================================================================================================
        #region ## Convert Functions ##

        public Point ToLocalCoord(Point pos)
        {
            return new Point((int)(pos.X / this.ZoomRate) + m_viewer.X,
                             (int)(pos.Y / this.ZoomRate) + m_viewer.Y);
        }

        public Point FromLocalCoord(Point pos)
        {
            return new Point((int) ((pos.X - m_viewer.X) * this.ZoomRate),
                             (int) ((pos.Y - m_viewer.Y) * this.ZoomRate));
        }

        protected MouseEventArgs ToWFMouseEvent(WpfInput.MouseEventArgs e)
        {
            WpfCore.Point pos = e.GetPosition(e.Source as System.Windows.IInputElement);
            System.Windows.Forms.MouseButtons mb = System.Windows.Forms.MouseButtons.None;

            WpfInput.MouseButtonEventArgs me = e as WpfInput.MouseButtonEventArgs;

            if (me != null)
            {
                if(me.ChangedButton == WpfInput.MouseButton.Left)
                    mb = System.Windows.Forms.MouseButtons.Left;
            }
            else
            {
                if (e.LeftButton == WpfInput.MouseButtonState.Pressed)
                    mb = System.Windows.Forms.MouseButtons.Left;
            }
            return new MouseEventArgs(mb, 1, (int)pos.X, (int)pos.Y, 0);
        }

        protected KeyEventArgs ToWFKeyEvent(WpfInput.KeyEventArgs e)
        {
            Keys k = Keys.None;
            switch (e.Key)
            {
                case WpfInput.Key.A: k = Keys.A; break;
                case WpfInput.Key.B: k = Keys.B; break;
                case WpfInput.Key.C: k = Keys.C; break;
                case WpfInput.Key.D: k = Keys.D; break;
                case WpfInput.Key.E: k = Keys.E; break;
                case WpfInput.Key.F: k = Keys.F; break;
                case WpfInput.Key.G: k = Keys.G; break;
                case WpfInput.Key.H: k = Keys.H; break;
                case WpfInput.Key.I: k = Keys.I; break;
                case WpfInput.Key.J: k = Keys.J; break;
                case WpfInput.Key.K: k = Keys.K; break;
                case WpfInput.Key.L: k = Keys.L; break;
                case WpfInput.Key.M: k = Keys.M; break;
                case WpfInput.Key.N: k = Keys.N; break;
                case WpfInput.Key.O: k = Keys.O; break;
                case WpfInput.Key.P: k = Keys.P; break;
                case WpfInput.Key.Q: k = Keys.Q; break;
                case WpfInput.Key.R: k = Keys.R; break;
                case WpfInput.Key.S: k = Keys.S; break;
                case WpfInput.Key.T: k = Keys.T; break;
                case WpfInput.Key.X: k = Keys.X; break;
                case WpfInput.Key.U: k = Keys.U; break;
                case WpfInput.Key.V: k = Keys.V; break;
                case WpfInput.Key.W: k = Keys.W; break;
                case WpfInput.Key.Y: k = Keys.Y; break;
                case WpfInput.Key.Z: k = Keys.Z; break;
                case WpfInput.Key.Space: k = Keys.Space; break;
                default: break;
            }
            KeyEventArgs kea = new KeyEventArgs(k);
            return kea;
        }

        public void OnMouseDown(WpfInput.MouseEventArgs e)
        {
            OnMouseDown(ToWFMouseEvent(e));
        }

        public void OnMouseMove(WpfInput.MouseEventArgs e)
        {
            OnMouseMove(ToWFMouseEvent(e));
        }

        public void OnMouseUp(WpfInput.MouseEventArgs e)
        {
            OnMouseUp(ToWFMouseEvent(e));
        }

        public void OnKeyUp(WpfInput.KeyEventArgs e)
        {
            OnKeyUp(ToWFKeyEvent(e));
        }

        #endregion
        //================================================================================================

        public void OnLoad()
        {
            if (m_thread.IsAlive) return;
            m_watch = Stopwatch.StartNew();
            m_thread.Start();
        }

        public void OnSizeChanged(int w, int h)
        {
            if (m_content != null ? w == m_content.Width && h == m_content.Height : false)
                return;

            Thread changeSizeThread = new Thread(new ParameterizedThreadStart(
                    delegate(Object obj)
                    {
                        m_mutex.WaitOne();
                        Size newsize = (Size)obj;
                        if (System.Windows.Application.Current != null)
                            System.Windows.Application.Current.Dispatcher.Invoke(
                                System.Windows.Threading.DispatcherPriority.Loaded, new Action(
                                    delegate()
                                    {
                                        this.DoSizeChanged(newsize.Width, newsize.Height);
                                    }));
                        m_mutex.ReleaseMutex();
                    }
                ));
            changeSizeThread.Start(new Size(w, h));
        }

        private void DoSizeChanged(int w, int h)
        {
            m_display = new WriteableBitmap(w, h, 96, 96, WpfMedia.PixelFormats.Pbgra32, null);

            m_content = new Bitmap(m_display.PixelWidth, m_display.PixelHeight, m_display.BackBufferStride, PixelFormat.Format32bppArgb, m_display.BackBuffer);

            m_graph = Graphics.FromImage(m_content);

            m_graph.SmoothingMode = SmoothingMode.None;
            m_graph.PixelOffsetMode = PixelOffsetMode.HighSpeed;
            m_graph.CompositingMode = CompositingMode.SourceOver;
            m_graph.CompositingQuality = CompositingQuality.AssumeLinear;
            m_graph.InterpolationMode = InterpolationMode.NearestNeighbor;

            m_viewer.Size = new Size(m_display.PixelWidth, m_display.PixelHeight);

            HostImage.Source = m_display;
        }

        public void OnMouseDown(MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                IsDragDrop = false;

                Selection sel = m_layers["selection"].Drawables.First() as Selection;
                
                Point localLocation = ToLocalCoord(e.Location);
                
                List<Drawable> hits = new List<Drawable>();

                for (int i = 0; i < m_layers.Count; i++)
                {
                    if (!m_layers[i].IsVisible) continue;

                    for (int j = 0; j < m_layers[i].Drawables.Count; j++)
                    {
                        if (!m_layers[i].Drawables[j].IsVisible) continue;

                        if (m_layers[i].Drawables[j].Boundary.Contains(localLocation))
                            hits.Add(m_layers[i].Drawables[j]);
                    }
                }

                //clear grab point
                foreach (Drawable sd in sel.SelectedDrawables)
                    sd.Reserved = null;

                if (sel.SelectedDrawables.Count == 1)
                {
                    Drawable.SizingGrip[] sgs = sel.SelectedDrawables.First().SizingGrips;
                    for(int i = 0; i < sgs.Length; i++)
                    {
                        Rectangle rec = Rectangle.FromLTRB(sgs[i].Center.X - sgs[i].ActualRadii.Width,
                                                            sgs[i].Center.Y - sgs[i].ActualRadii.Height,
                                                            sgs[i].Center.X + sgs[i].ActualRadii.Width,
                                                            sgs[i].Center.Y + sgs[i].ActualRadii.Height);

                        if (rec.Contains(localLocation))
                        {
                            sel.SelectedDrawables.First().SelectedGripIndex = i;
                            return;
                        }
                    }
                }

                if (hits.Count > 0)
                {
                    int cur = 0;
                    for (cur = 0; cur < hits.Count && !hits[cur].IsSelected; cur++);
                    
                    if(cur < hits.Count)
                    {
                        // Set grab point
                        foreach (Drawable sd in sel.SelectedDrawables)
                            sd.Reserved = new Point(localLocation.X - sd.Location.X, localLocation.Y - sd.Location.Y);
                    }
                    else
                    {
                        if (sel.SelectedDrawables.Count > 0 && !Utils.IsControlKeyDown())
                            sel.SelectedDrawables.Clear();
                        sel.SelectedDrawables.Add(hits.First());
                    }
                }
                else
                {                 
                    m_layers["selection"].Drawables.First().IsVisible = true;
                    m_layers["selection"].Drawables.First().Dimension = Size.Empty;
                    m_layers["selection"].Drawables.First().Location = localLocation;
                }
            }
        }

        public void OnMouseMove(MouseEventArgs e)
        {
            Selection sel = m_layers["selection"].Drawables.First() as Selection;

            Point localLocation = ToLocalCoord(e.Location);

            if (e.Button == MouseButtons.Left)
            {               
                if (m_layers["selection"].Drawables.First().IsVisible)
                {
                    Rectangle rec = m_layers["selection"].Drawables.First().Boundary;
                    m_layers["selection"].Drawables.First().Boundary = Rectangle.FromLTRB(rec.Left, rec.Top, localLocation.X, localLocation.Y);
                }
                else
                {
                    if (sel.SelectedDrawables.Count == 1)
                    {
                        if (sel.SelectedDrawables.First().SelectedGrip != null)
                        {
                            sel.SelectedDrawables.First().SelectedGrip.Center = localLocation;
                            return;
                        }
                    }

                    foreach (Drawable sd in sel.SelectedDrawables)
                    {
                        if (sd.Reserved != null)
                        {
                            Point p = (Point)sd.Reserved;
                            sd.Location = new Point(localLocation.X - p.X, localLocation.Y - p.Y);
                            IsDragDrop = true;
                        }

                    }
                }
            }

            if (e.Button == MouseButtons.None)
            {
                if (sel.SelectedDrawables.Count == 1)
                {
                    foreach (Drawable.SizingGrip sg in sel.SelectedDrawables.First().SizingGrips)
                    {
                        Rectangle rec = Rectangle.FromLTRB(sg.Center.X - sg.ActualRadii.Width,
                                                            sg.Center.Y - sg.ActualRadii.Height,
                                                            sg.Center.X + sg.ActualRadii.Width,
                                                            sg.Center.Y + sg.ActualRadii.Height);

                        if (rec.Contains(localLocation))
                        {
                            HostImage.Cursor = WpfInput.Cursors.Help;
                            return;
                        }
                    }
                    HostImage.Cursor = WpfInput.Cursors.Arrow;
                }
            }
        }

        public void OnMouseUp(MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                Selection sel = m_layers["selection"].Drawables.First() as Selection;

                sel.IsVisible = false;

                Rectangle fixedRec = new Rectangle(
                    (int)(Math.Min(sel.Boundary.Left, sel.Boundary.Right) * ZoomRate),
                    (int)(Math.Min(sel.Boundary.Top, sel.Boundary.Bottom) * ZoomRate),
                    (int)(Math.Abs(sel.Boundary.Width) * ZoomRate),
                    (int)(Math.Abs(sel.Boundary.Height) * ZoomRate)
                );

                if (sel.Boundary.Width * sel.Boundary.Height > 0)
                {
                    sel.Dimension = Size.Empty;
                }
                else
                {
                    if (IsDragDrop) return;

                    Point localLocation = ToLocalCoord(e.Location);

                    List<Drawable> hits = new List<Drawable>();

                    for (int i = 0; i < m_layers.Count; i++)
                    {
                        if (!m_layers[i].IsVisible) continue;

                        for (int j = 0; j < m_layers[i].Drawables.Count; j++)
                        {
                            if (!m_layers[i].Drawables[j].IsVisible) continue;

                            if (m_layers[i].Drawables[j].Boundary.Contains(localLocation))
                                hits.Add(m_layers[i].Drawables[j]);
                        }
                    }

                    if (sel.SelectedDrawables.Count == 1) // release sizing-grip , not allow user to resize anymore 
                    {
                        sel.SelectedDrawables.First().SelectedGripIndex = Drawable.SizingGrip.None; 
                    }

                    if (hits.Count > 0)
                    {
                        if (Utils.IsControlKeyDown())
                        {
                            int cur = 0;
                            for (cur = 0; cur < hits.Count && hits[cur].IsSelected; cur++) ;

                            if (cur < hits.Count)
                            {
                                sel.SelectedDrawables.Add(hits[cur]);
                            }
                            else
                            {                              
                                if (sel.SelectedDrawables.First().IsSelected && hits.First().Reserved != null)
                                    sel.SelectedDrawables.Remove(hits.First());
                            }
                        }
                        else
                        {
                            int cur = 0;
                            for (cur = 0; cur < hits.Count && !hits[cur].IsSelected; cur++) ;

                            if (cur >= hits.Count || hits[cur].Equals(hits.Last()))
                            {
                                sel.SelectedDrawables.Clear();
                                sel.SelectedDrawables.Add(hits.First());
                            }
                            else
                            {
                                sel.SelectedDrawables.Clear();
                                sel.SelectedDrawables.Add(hits[++cur]);
                            }
                        }
                    }
                }
            }
        }

        public void OnKeyUp(KeyEventArgs e)
        {
            
        }

        protected void UpdateScene()
        {
            int mpf = 1000 / Document.MaxFPS;

            while (System.Windows.Application.Current != null) //TODO: add a condition to stop this infinite loop
            {
                m_mutex.WaitOne();
                Int32 ems = (Int32)m_watch.ElapsedMilliseconds;
                if (mpf > ems)
                {
                    Thread.Sleep(mpf - ems);
                }
                m_watch.Reset();
                
                if (HostImage != null)
                    HostImage.Dispatcher.Invoke(
                        System.Windows.Threading.DispatcherPriority.ContextIdle, new Action(
                            delegate()
                            {
                                this.StartScene();
                            }));

                this.ClearWorkspace();

                for (int i = m_layers.Count - 1; i >= 0; i--)
                {
                    if (!m_layers[i].IsVisible) continue; //user hide it no need to draw layer anymore

                    for (int j = m_layers[i].Drawables.Count - 1; j >= 0; j--)
                    {
                        if (!m_layers[i].Drawables[j].IsVisible) continue;
                        m_layers[i].Drawables[j].OnUpdate(mpf);
                        m_layers[i].Drawables[j].OnDraw(this);
                    }
                }

                if (HostImage != null)
                    HostImage.Dispatcher.Invoke(
                        System.Windows.Threading.DispatcherPriority.ContextIdle, new Action(
                            delegate(){
                                this.EndScence();
                            }));


                m_mutex.ReleaseMutex();
                   
            }
            m_content.Dispose();
            m_graph.Dispose();
        }

        protected void StartScene()
        {
            if (m_display != null)
            {
                m_display.Lock();
            }
        }

        protected void EndScence()
        {
            if (m_display != null)
            {
                m_display.AddDirtyRect(new System.Windows.Int32Rect(0, 0, m_display.PixelWidth, m_display.PixelHeight));
                m_display.Unlock();
            }
        }

        protected void ClearWorkspace()
        {
            try
            {
                m_graph.Clip = new Region(new Rectangle(0, 0, m_viewer.Width, m_viewer.Height));

                float[] dashValues = { 8, 8 };
                Pen dashPen = new Pen(Color.LightGray, 1);
                dashPen.DashPattern = dashValues;
                m_graph.FillRectangle(new SolidBrush(Color.White), 0, 0, m_viewer.Width, m_viewer.Height);

                Point origin = new Point((int)(-m_viewer.X * this.ZoomRate), (int)(-m_viewer.Y * this.ZoomRate));

                // draw checkerboard background
                int dx = (int)Math.Ceiling(origin.X / 16.0f);
                int dy = (int)Math.Ceiling(origin.Y / 16.0f);
                Point topLeft = new Point(origin.X - (16 * dx), origin.Y - (16 * dy));
                int line = 0;
                while ((topLeft.Y + line) < m_viewer.Height)
                {
                    if ((topLeft.Y + line) >= 0)
                    {
                        int xOffset = ((line / 8) % 2).Equals(0) ? 0 : 8;
                        m_graph.DrawLine(dashPen, topLeft.X + xOffset, line + topLeft.Y, m_viewer.Width, line + topLeft.Y);
                    }
                    line++;
                }

                // draw x-axis and y-axis
                if (this.IsAxesVisible)
                {
                    float[] axesDashValues = { 2, 2 };
                    Pen axesDashPen = new Pen(Color.Red, 1);
                    axesDashPen.DashPattern = axesDashValues;
                    m_graph.DrawLine(axesDashPen, 0, origin.Y, m_viewer.Width, origin.Y);
                    m_graph.DrawLine(axesDashPen, origin.X, 0, origin.X, m_viewer.Height);
                }
            }
            catch (Exception)
            {

            }
        }
    }
}
